﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using agsXMPP;
using System.Threading;
using Newtonsoft.Json;
using agsXMPP.protocol.client;

namespace Zennect.IPC.Jabber {
	public delegate void MessageRecievedHandler(string user, string resource, object content);

	public class JabberIPC {
		XmppClientConnection _jabberClient;
		string _host;

		AutoResetEvent _aur;

		Dictionary<Guid, AutoResetEvent> _handles;
		Dictionary<Guid, string> _responses;

		#region events
		public event MessageRecievedHandler OnMessage;
		#endregion

		public JabberIPC(string host, string user, string password)
			: this(host, user, password, Environment.MachineName) {
		}

		public JabberIPC(string host, string user, string password, string resource) {
			_host = host;

			_jabberClient = new XmppClientConnection(host);

			_jabberClient.OnRegisterError += new XmppElementHandler(jabberClient_OnRegisterError);
			_jabberClient.OnRegistered += new ObjectHandler(jabberClient_OnRegistered);

			_jabberClient.OnAuthError += new XmppElementHandler(jabberClient_OnAuthError);
			_jabberClient.OnLogin += new ObjectHandler(jabberClient_OnLogin);

			_jabberClient.OnMessage += new agsXMPP.protocol.client.MessageHandler(jabberClient_OnMessage);
			_jabberClient.OnError += new ErrorHandler(jabberClient_OnError);

			try {
				_jabberClient.Open(user, password, resource);
			} catch (Zennect.IPC.Jabber.Exceptions.AuthorizationException authExc) {
				try {
					_jabberClient.RegisterAccount = true;
					_jabberClient.Open(user, password, resource);
				} catch (Zennect.IPC.Jabber.Exceptions.RegistrationException) {
					throw authExc;
				}
			}

			_handles = new Dictionary<Guid, AutoResetEvent>();
			_responses = new Dictionary<Guid, string>();

			_aur = new AutoResetEvent(false);
			_aur.WaitOne();
		}

		~JabberIPC() {
			_jabberClient.Close();
		}

		void jabberClient_OnError(object sender, Exception ex) {
			throw ex;
		}

		public void SendOneWayMessage(string to, object content) {
			string json = JavaScriptConvert.SerializeObject(content);

			if (!to.Contains("@"))
				to += "@" + _host;

			var msg = new Message(to, MessageType.chat, json);

			_jabberClient.Send(msg);
		}

		public void SendMessage<T>(string to, object content, Action<T> callback) {
			(new Thread(() => callback(SendMessage<T>(to, content)))).Start();
		}

		public T SendMessage<T>(string to, object content) {
			Guid id = Guid.NewGuid();

			string msgText = String.Format("{{{0}}}||{1}", id, JavaScriptConvert.SerializeObject(content));

			if (!to.Contains("@"))
				to += "@" + _host;

			var msg = new Message(to, MessageType.chat, msgText);

			AutoResetEvent rEvent = new AutoResetEvent(false);

			_handles.Add(id, rEvent);

			_jabberClient.Send(msg);
			rEvent.WaitOne();

			_handles.Remove(id);

			string retStr = _responses[id];
			_responses.Remove(id);

			return JavaScriptConvert.DeserializeObject<T>(retStr);
		}

		void jabberClient_OnMessage(object sender, agsXMPP.protocol.client.Message msg) {
			if (msg.Type == MessageType.chat || msg.Type == MessageType.groupchat) {
				string strContent = msg.Body;

				if (strContent.IndexOf("||") == 38) {
					Guid id = new Guid(strContent.Substring(0, 38));
					strContent = strContent.Substring(40);

					_responses.Add(id, strContent);

					if (_handles.ContainsKey(id))
						_handles[id].Set();
				}

				if (this.OnMessage != null) {
					try {
						var content = JavaScriptConvert.DeserializeObject(msg.Body);
						OnMessage(msg.From.User, msg.From.Resource, content);
					} catch {
					}
				}
			} else if (msg.Type == MessageType.error) {
				throw new Exception(msg.Body);
			}

		}

		#region Login Related Event Handlers
		void jabberClient_OnLogin(object sender) {
			_aur.Set();
		}

		void jabberClient_OnAuthError(object sender, agsXMPP.Xml.Dom.Element e) {
			_aur.Set();
			throw new Zennect.IPC.Jabber.Exceptions.AuthorizationException("Could not authorize");
		}

		void jabberClient_OnRegistered(object sender) {
			_aur.Set();
		}

		void jabberClient_OnRegisterError(object sender, agsXMPP.Xml.Dom.Element e) {
			_aur.Set();
			throw new Zennect.IPC.Jabber.Exceptions.RegistrationException("Could not register");
		}
		#endregion
	}
}
